package roboGP;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

/**
 * Classe principale della programmazione genetica, in cui avviene il ciclo dell'evoluzione.
 */
public class EvolutionChamber 
{
	public static final String PATH = "robocode/robots/metaRobot";
	public static final String PACKAGE = "metaRobot";
	public static final String JARS = "robocode/libs/robocode.jar";
	public static final String ACTIONS_CONDITIONS_PATH = "txt/";
	
	public static final int POPULATION_SIZE = 150, 
					 GENERATION_NUMBER = 150,
					 ROUND = 25,
					 TOURNAMENT_ROUND = 4, 
					 INIT_DEPTH_MAIN = 3,
					 INIT_DEPTH_ONSCANNED = 3,
					 INIT_DEPTH_OTHERS = 2,
					 MAX_DEPTH_MAIN = 7,
					 MAX_DEPTH_ONSCANNED = 7,
					 MAX_DEPTH_OTHERS = 3;
	
	public static final double PROB_CROSSOVER = 0.85,
						PROB_MUTATION = 0.05,
						PROB_SUBTREE_MUTATION = 0.05,
						PROB_COPY = 0.05;
	
	public static final boolean DOUBLE_CROSSOVER = true;
	private static boolean PARALLEL_EXEC = false;
	
	static ArrayList<String> actionMain;
	static ArrayList<String> actionByBullet;
	static ArrayList<String> actionOnHitWall;
	static ArrayList<String> actionOnScanned;
	static ArrayList<String> actionOnHitRobot;
	static ArrayList<String> condMain;
	static ArrayList<String> condByBullet;
	static ArrayList<String> condOnHitWall;
	static ArrayList<String> condOnScanned;
	static ArrayList<String> condOnHitRobot;
	
	static MetaRobot[] metaRobots;
	/*
	private static String[] adversaries = { "adversariesRobot.SuperRamFire*",
											"adversariesRobot.SuperSpinBot*",
											"adversariesRobot.SuperWalls*",
											"adversariesRobot.SuperTracker*"};
	
	private static double[] weight = {0.2848, 0.2911, 0.2088, 0.2152};
	*/
	
	private static String[] adversaries = { "adversariesRobot.Fire*",
											"adversariesRobot.SpinBot*",
											"adversariesRobot.VelociRobot*",
											"adversariesRobot.Walls*"};
	
	private static double[] weight = {0.0512, 0.2692, 0.2564, 0.4231};
	
	
	public static void main(String[] args)
	{
		BattleRunner arena;
		if (PARALLEL_EXEC)
			arena = new BattleRunnerMaster();
		else
			arena = new BattleRunner();
		
		long time = System.currentTimeMillis();
		MetaRobot.compileACRobot();
		BattleRunner.newFileResults(POPULATION_SIZE, GENERATION_NUMBER, ROUND, TOURNAMENT_ROUND, PROB_CROSSOVER, PROB_MUTATION, PROB_SUBTREE_MUTATION, PROB_COPY);
		
		importNodesDictionaries();
		System.out.println("Dizionari importati.");
		initPop();
		System.out.println("Popolazione iniziale creata.\n");
		
		int cross;
		if (DOUBLE_CROSSOVER)
			cross = (int) ((PROB_CROSSOVER*POPULATION_SIZE)/2);
		else
			cross = (int) (PROB_CROSSOVER*POPULATION_SIZE);
		
		int mut = (int) (PROB_MUTATION*POPULATION_SIZE);
		int sub_mut = (int) (PROB_SUBTREE_MUTATION*POPULATION_SIZE);
		
		int copy = POPULATION_SIZE - mut - sub_mut;
		if (DOUBLE_CROSSOVER)
			copy = copy - 2*cross;
		else
			copy = copy - cross;
		
		int nBot;
		for(int i = 0; i < GENERATION_NUMBER ; i++) {
			nBot=0;
			arena.runBatchWithSAversaries(metaRobots, adversaries, weight, ROUND);
			MetaRobot best = MetaRobot.findBest(metaRobots);
			BattleRunner.saveResults(i,MetaRobot.findBest(metaRobots).getFitness(), MetaRobot.averageFitness(metaRobots), MetaRobot.findWorst(metaRobots).getFitness());
			System.out.println("\nIl miglior Robot è "+best.getName()+" con fitness = "+best.getFitness());
			MetaRobot[] nextGen = new MetaRobot[POPULATION_SIZE];

			System.out.println("CROSSOVER = "+cross+" MUTATION = "+mut+" SUBTREE MUTATION = "+sub_mut+" COPY = "+copy);
			System.out.print("Inizio riproduzione...");
			
			for (int j=0; j<cross;j++) {
				if (DOUBLE_CROSSOVER)
				{
					MetaRobot[] crossedRobots = MetaRobot.robotCrossover(metaRobots, TOURNAMENT_ROUND, i+1, nBot);
					nextGen[nBot] = crossedRobots[0];
					nextGen[nBot+1] = crossedRobots[1];
					nBot+=2;
				}
				else
				{
					nextGen[nBot] = MetaRobot.singleRobotCrossover(metaRobots, TOURNAMENT_ROUND, i+1, nBot);;
					nBot++;
				}
			}
			for (int j=0;j<mut;j++) {
				MetaRobot mb = MetaRobot.mutateRobot(metaRobots, false, TOURNAMENT_ROUND, i+1, nBot);
				nextGen[nBot] = mb;
				nBot++;
			}
			for (int j=0;j<sub_mut;j++) {
				MetaRobot mb = MetaRobot.mutateRobot(metaRobots, true, TOURNAMENT_ROUND, i+1, nBot);
				nextGen[nBot] = mb;
				nBot++;
			}
			ArrayList<MetaRobot> mr = MetaRobot.findBest(metaRobots, copy);
			for (int k = 0; k < copy; k++)
			{
				nextGen[nBot] = new MetaRobot(i+1,nBot,mr.get(k).getGenoma());
				nBot++;
			}
			metaRobots = nextGen;
			System.out.print("inizio compilazione...");
			createNewGeneration();
			MetaRobot.clearBots(best.getNRobot(),i, POPULATION_SIZE);
			System.out.println("generazione " + (i+1) + " creata.");
		}
		BattleRunner.closeResults();
		
		time = (System.currentTimeMillis() - time)/1000;
		long hours = time/3600;
		long minutes = (time - hours*3600)/60;
		long seconds = time - hours*3600 - minutes*60;
		System.out.println("Evoluzione terminata in " + hours + " ore " + minutes + " minuti " + seconds + " secondi.");
	}
	
	/**
	 * Importa da file tutte le possibili condizioni/azioni relative
	 * agli eventi di Robocode.
	 */
	private static void importNodesDictionaries() {
		File actMain = new File (ACTIONS_CONDITIONS_PATH + "actionsMain.txt");
		File cMain = new File (ACTIONS_CONDITIONS_PATH + "conditionsMain.txt");
		File cOnHitW = new File (ACTIONS_CONDITIONS_PATH + "conditionsOnHitWall.txt");
		File actOnHitW = new File (ACTIONS_CONDITIONS_PATH + "actionsOnHitWall.txt");
		File cOnHitRobot = new File (ACTIONS_CONDITIONS_PATH + "conditionsOnHitRobot.txt");
		File actOnHitRobot = new File (ACTIONS_CONDITIONS_PATH + "actionsOnHitRobot.txt");
		File cOnScanned = new File (ACTIONS_CONDITIONS_PATH + "conditionsOnScannedRobot.txt");
		File actOnScanned = new File (ACTIONS_CONDITIONS_PATH + "actionsOnScannedRobot.txt");
		File cOnByBullet = new File (ACTIONS_CONDITIONS_PATH + "conditionsOnByBullet.txt");
		File actOnByBullet = new File (ACTIONS_CONDITIONS_PATH + "actionsOnByBullet.txt");
		
		condMain = new ArrayList<String>();
		condOnHitWall = new ArrayList<String>();
		condOnHitRobot = new ArrayList<String>();
		condOnScanned = new ArrayList<String>();
		condByBullet = new ArrayList<String>();
		actionMain = new ArrayList<String>();
		actionOnHitWall = new ArrayList<String>();
		actionOnHitRobot = new ArrayList<String>();
		actionOnScanned = new ArrayList<String>();
		actionByBullet =  new ArrayList<String>();
 
		importFile(actMain, actionMain);
		importFile(cMain, condMain);
		importFile(cOnHitW, condOnHitWall);
		importFile(actOnHitW, actionOnHitWall);
		importFile(cOnHitRobot, condOnHitRobot);
		importFile(actOnHitRobot, actionOnHitRobot);
		importFile(cOnScanned, condOnScanned);
		importFile(actOnScanned, actionOnScanned);
		importFile(cOnByBullet, condByBullet);	
		importFile(actOnByBullet, actionByBullet);	
	}
	
	private static void importFile(File file, ArrayList<String> list) {
		try {
			FileInputStream fis = new FileInputStream(file);
		 
			//Construct BufferedReader from InputStreamReader
			BufferedReader br = new BufferedReader(new InputStreamReader(fis));
			String line = null;
			while ((line = br.readLine()) != null) {
				list.add(line);
			}
			br.close();
		} catch(IOException e)
		{
			System.out.println(e.getMessage());
		}
	}
	
	/**
	 * Inizializza la popolazione, creando la prima generazione.
	 */
	private static void initPop() {
		metaRobots = new MetaRobot[POPULATION_SIZE];
		
		RobotGenoma.DepthsVector initDepths = new RobotGenoma.DepthsVector
				(INIT_DEPTH_MAIN, INIT_DEPTH_ONSCANNED, INIT_DEPTH_OTHERS, INIT_DEPTH_OTHERS, INIT_DEPTH_OTHERS);
		RobotGenoma.DepthsVector maxDepths = new RobotGenoma.DepthsVector
				(MAX_DEPTH_MAIN, MAX_DEPTH_ONSCANNED, MAX_DEPTH_OTHERS, MAX_DEPTH_OTHERS, MAX_DEPTH_OTHERS);
		
		for (int i = 0; i < POPULATION_SIZE; i++)
		{
			RobotGenoma rg = new RobotGenoma(initDepths, maxDepths);
			metaRobots[i] = new MetaRobot(0, i, rg);
			metaRobots[i].createRobot();
		}
	}
	
	/**
	 * Crea una nuova generazione compilando i sorgenti con una unica invocazione a javac.
	 */
	private static void createNewGeneration()
	{
		for (int i = 0; i < POPULATION_SIZE; i++)
		{
			metaRobots[i].createRobot();
		}
		
		try {
			MetaRobot.execute("javac -cp " + JARS + " " + PATH + "/ACRobot.java" + " " + PATH + "/" + "*.java");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}
		
}
